#include "Base64Coder.h"

using namespace kc_sf;

// Inicjalizacja stałych statycznych -------------------------------- //

const unsigned char 
Base64Coder::Base64Alphabet[ Base64Coder::Base64CharNr + 1 ] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

const std::string Base64Coder::CRLF = "\r\n";

// Konstruktory ----------------------------------------------------- //

Base64Coder::Base64Coder( unsigned int encoded_line_length,
                          bool add_crlf_at_end )
{
    bc_line_length = ( encoded_line_length >= Base64MaxLineLength ) ?
                        Base64MaxLineLength : encoded_line_length;
    bc_add_crlf_at_end = add_crlf_at_end;
}

// Kodowanie i dekodowanie napisów ---------------------------------- //

bool Base64Coder::encode( const std::string &input, 
                          std::string &output ) const
{
    output.clear();
    bool success = true;

    unsigned int size = input.size(),
                 line_length = 0,
                 pad = size % 3;
    const int encoded_nr = 4;

    for( unsigned int i = 0; i < size; i += 3 )
    {        
        int enc[encoded_nr + 1] = { -1, -1, -1, -1 };

        enc[0] = static_cast<unsigned char>(input[i]) >> 2;   // niejawna konwersja z unsigned char na int
        
        if( i == size - pad && pad != 0 )   // ostatnia trojka bajtow
        {
            if( pad == 1 )                  // brakuje 2 bajtow
                enc[1] = ( static_cast<unsigned char>(input[i]) << 4 ) 
                         & 0x30;
            else if( pad == 2 )             // brakuje 1 bajtu
            {
                enc[1] = ( ( static_cast<unsigned char>(input[i]) << 4 ) & 0x30 )
                         | ( static_cast<unsigned char>(input[i + 1]) >> 4 );
                enc[2] = ( static_cast<unsigned char>( input[i + 1] ) << 2 ) & 0x3c;
            }
        }
        else                                // pelna trojka bajtow
        {
            enc[1] = ( ( static_cast<unsigned char>(input[i]) << 4 ) & 0x30 )
                     | ( static_cast<unsigned char>( input[i + 1] ) >> 4 );
            enc[2] = ( ( static_cast<unsigned char>( input[i + 1] ) << 2 ) & 0x3c )
                     | ( static_cast<unsigned char>( input[i + 2] ) >> 6 );
            enc[3] = static_cast<unsigned char>( input[i + 2] ) & 0x3f;
        }
            
        // wypisywanie do strumienia wyjsciowego
        for( int j = 0; j < encoded_nr; ++j )
        {
            if( enc[j] == -1 )              // brakuje bajtu 
                output += '=';
            else
            {
                // wychodzi poza tablice
                if( enc[j] > Base64CharNr )
                {
                    std::cerr << enc[j] << "Base64 converter error!" << std::endl;
                    break;  // blad = przerwanie
                }
                
                output += Base64Alphabet[ enc[j] ];
            }
                
            ++ line_length;    

            // czy przekroczono limit znakow na linie
            if( line_length >= bc_line_length ) { 
                line_length = 0;
                output += CRLF;
            }
        }        
    }

	// dodaj nowa linie na koncu
	if( bc_add_crlf_at_end && line_length != 0 )
		output += CRLF;
        
    return success;
}

bool Base64Coder::encode( const char *input, unsigned int input_size, 
                          std::string &output ) const
{
    output.clear();
    bool success = true;

    unsigned int size = input_size,
                 line_length = 0,
                 pad = size % 3;
    const int encoded_nr = 4;

    for( unsigned int i = 0; i < size; i += 3 )
    {        
        int enc[encoded_nr + 1] = { -1, -1, -1, -1 };

        enc[0] = static_cast<unsigned char>(input[i]) >> 2;   // niejawna konwersja z unsigned char na int
        
        if( i == size - pad && pad != 0 )   // ostatnia trojka bajtow
        {
            if( pad == 1 )                  // brakuje 2 bajtow
            {
                enc[1] = ( static_cast<unsigned char>(input[i]) << 4 ) 
                         & 0x30;
            }
            else if( pad == 2 )             // brakuje 1 bajtu
            {
                enc[1] = ( ( static_cast<unsigned char>(input[i]) << 4 ) & 0x30 )
                         | ( static_cast<unsigned char>(input[i + 1]) >> 4 );
                enc[2] = ( static_cast<unsigned char>( input[i + 1] ) << 2 ) & 0x3c;
            }
        }
        else                                // pelna trojka bajtow
        {
            enc[1] = ( ( static_cast<unsigned char>(input[i]) << 4 ) & 0x30 )
                     | ( static_cast<unsigned char>( input[i + 1] ) >> 4 );
            enc[2] = ( ( static_cast<unsigned char>( input[i + 1] ) << 2 ) & 0x3c )
                     | ( static_cast<unsigned char>( input[i + 2] ) >> 6 );
            enc[3] = static_cast<unsigned char>( input[i + 2] ) & 0x3f;
        }
            
        // wypisywanie do strumienia wyjsciowego
        for( int j = 0; j < encoded_nr; ++j )
        {
            if( enc[j] == -1 )              // brakuje bajtu 
                output += '=';
            else
            {
                // wychodzi poza tablice
                if( enc[j] > Base64CharNr )
                {
                    std::cout << enc[j] << "Base64 converter error!" << std::endl;
                    break;  // blad = przerwanie
                }
                
                output += Base64Alphabet[ enc[j] ];
            }
                
            ++ line_length;    

            // czy przekroczono limit znakow na linie
            if( line_length >= bc_line_length ) { 
                line_length = 0;
                output += CRLF;
            }
        }        
    }

	// dodaj nowa linie na koncu
	if( bc_add_crlf_at_end && line_length != 0 )
		output += CRLF;
        
    return success;
}

bool Base64Coder::decode( const std::string &input, 
                          std::string &output ) const
{
    output.clear();
    bool success = true;
    unsigned int omitted = 0;
            
#ifdef DEBUG 
int nrs = 0;
#endif

    for( unsigned int i = 0; i < input.size(); i += 4 + omitted )
    {
        // domyslny koniec kodowania - zgodnie z rfc2045
        if( input[i] == '=' )
            break;
            
        omitCRLF( input, i );                   // CRLF do pominiecia
        if( i == input.size() )     break;
        
        // wyznaczanie indeksow w tablicy alfabetu base64
        int in = index( input[i] ), in1, in2, in3;
        unsigned int j = i;

        omitted = omitCRLF( input, ++ j );     // CRLF do pominiecia
        if( j == input.size() )     break;     // blad - za malo bajtow

        in1 = index( input[j] );

        omitted += omitCRLF( input, ++ j );    // CRLF do pominiecia
        if( j == input.size() )     break;     // blad - za malo bajtow

        in2 = index( input[j] );

        omitCRLF( input, ++ j );               // CRLF do pominiecia
        if( j == input.size() )     break;     // blad - za malo bajtow

        in3 = index( input[j] );

        // zapis do napisu wyjsciowego
        //unsigned 
        char _byte = ( in << 2 ) | ( (in1 >> 4) & 0x3 );
        output += _byte;
        
#ifdef DEBUG 
for( int z = 7; z >= 0; -- z )
{
    if( ( output[output.size() - 1] >> z & 0x01 ) == 1 )
        std::cout << "1";
    else
        std::cout << "0";
}
    ++ nrs;
std::cout << " ";

if( nrs != 0 && nrs % 8 == 0 )
std::cout << std::endl;
#endif
        // domyslny koniec kodowania - zgodnie z rfc2045
        if( input[i + 2] == '=' )
            break;

        _byte = in1 << 4 | ( (in2 >> 2) & 0xf );
        output += _byte;

#ifdef DEBUG 
for( int z = 7; z >= 0; -- z )
{
    if( ( output[output.size() - 1] >> z & 0x01 ) == 1 )
        std::cout << "1";
    else
        std::cout << "0";
}
    ++ nrs;
std::cout << " ";

if( nrs != 0 && nrs % 8 == 0 )
std::cout << std::endl;
#endif

        // domyslny koniec kodowania - zgodnie z rfc2045
        if( input[i + 3] == '=' ) 
            break;

        _byte = in2 << 6 | in3;
        output += _byte;

#ifdef DEBUG 
for( int z = 7; z >= 0; -- z )
{
    if( ( output[output.size() - 1] >> z & 0x01 ) == 1 )
        std::cout << "1";
    else
        std::cout << "0";
}
    ++ nrs;
std::cout << " ";

if( nrs != 0 && nrs % 8 == 0 )
std::cout << std::endl;
#endif
    }
    
    return success;
}

bool Base64Coder::decode( const std::string &input, 
                          char *output, unsigned int &output_size ) const
{    
    bool success = true;
    unsigned int omitted = 0;
         
    unsigned int k = 0;        
    for( unsigned int i = 0; 
         i < input.size() && k < output_size; 
         i += 4 + omitted, k += 3 )
    {
        // domyslny koniec kodowania - zgodnie z rfc2045
        if( input[i] == '=' )
            break;
            
        omitCRLF( input, i );                   // CRLF do pominiecia
        if( i == input.size() )     break;
        
        // wyznaczanie indeksow w tablicy alfabetu base64
        int in = index( input[i] ), in1, in2, in3;
        unsigned int j = i + 1;

        omitted = omitCRLF( input, j );        // CRLF do pominiecia
        if( j == input.size() )     break;     // blad - za malo bajtow

        in1 = index( input[j] );

        omitted += omitCRLF( input, ++ j );    // CRLF do pominiecia
        if( j == input.size() )     break;     // blad - za malo bajtow

        in2 = index( input[j] );

        omitCRLF( input, ++ j );               // CRLF do pominiecia
        if( j == input.size() )     break;     // blad - za malo bajtow

        in3 = index( input[j] );

        // zapis do napisu wyjsciowego
        unsigned char _byte = in << 2 | in1 >> 4;
        output[k] = _byte;

        // domyslny koniec kodowania - zgodnie z rfc2045
        if( input[i + 2] == '=' )
            break;

        _byte = 
            in1 << 4 | in2 >> 2;
        output[k + 1] = _byte;

        // domyslny koniec kodowania - zgodnie z rfc2045
        if( input[i + 3] == '=' ) 
            break;

        _byte = in2 << 6 | in3;
        output[k + 2] = _byte;
    }
    
    output[k] = '\0';
    output_size = k + 1;
    return success;
}

// Pomocnicze ------------------------------------------------------- //

// odwzorowanie literek na indeks w tablicy alfabetu base64
int Base64Coder::index( unsigned char c ) const
{
    int ret = error;

    if( c == '\n' || c == '\r' )    {
        ret = new_line;
        return ret;
    }
    if( c == '=' )    {
        ret = equal;
        return ret;
    }

    if( c >= 'A' && c <= 'Z' )
        ret = c - 'A';
    else if( c >= 'a' && c <= 'z' )
        ret = c - 'a' + 26;
    else if( c >= '0' && c <= '9' )
        ret = c - '0' + 52;
    else if( c == '+' )
        ret = 62;
    else if( c == '/' )
        ret = 63;
    else
        ret = 0;
        
    return ret;
}

/** @param i    pozycja wejsciowa w napisie, staje się pierwszą pozycją 
 * w napisie po ominieciu niepotrzebnych znakow 
 * @return  ilość pominiętych znaków 
 */
unsigned int 
Base64Coder::omitCRLF( const std::string &input, unsigned int &i ) const
{
    unsigned int omitted = 0;
    
    while( i < input.size() 
           && ( input[i] == '\r' || input[i] == '\n' ) )    {
        ++ omitted;
        ++ i;
    }
    
    return omitted;
}
